package org.xx.armory.spring5.jdbc;

import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.jdbc.core.ResultSetExtractor;

import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Date;

import static org.xx.armory.commons.Validators.rejectIfNull;

/**
 * 提供了一组映射方法，用于映射单独的一个值。
 */
public final class ScalarMappers {
    public static final ScalarMapper<Integer> INTEGER_MAPPER = mapToInt(1);

    public static final ScalarMapper<Short> SHORT_MAPPER = mapToShort(1);

    public static final ScalarMapper<Long> LONG_MAPPER = mapToLong(1);

    public static final ScalarMapper<Double> DOUBLE_MAPPER = mapToDouble(1);

    public static final ScalarMapper<Float> FLOAT_MAPPER = mapToFloat(1);

    public static final ScalarMapper<BigDecimal> BIG_DECIMAL_MAPPER = mapToBigDecimal(1);

    public static final ScalarMapper<Date> DATE_MAPPER = mapToDate(1);

    public static final ScalarMapper<Boolean> BOOLEAN_MAPPER = mapToBoolean(1);

    public static final ScalarMapper<String> STRING_MAPPER = mapToString(1);

    public static final ScalarMapper<byte[]> BYTES_MAPPER = mapToBytes(1);

    public static final ScalarMapper<Object[]> OBJECTS_MAPPER = mapToObjects();

    private ScalarMappers() {
        throw new AssertionError();
    }

    public static ScalarMapper<Integer> mapToInt(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Integer extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getInteger(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Long> mapToLong(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Long extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getLong(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Double> mapToDouble(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Double extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getDouble(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Float> mapToFloat(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Float extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getFloat(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Short> mapToShort(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Short extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getShort(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<BigDecimal> mapToBigDecimal(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected BigDecimal extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getBigDecimal(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Date> mapToDate(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Date extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getDate(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Boolean> mapToBoolean(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected Boolean extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getBoolean(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<byte[]> mapToBytes(
            int columnIndex
    ) {
        return new ScalarMapper<>() {
            @Override
            protected byte[] extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getBytes(resultSet, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<String> mapToString(
            int columnIndex
    ) {
        return new ScalarMapper<>(columnIndex) {
            @Override
            protected String extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getString(resultSet, getColumnIndex());
            }

            @Override
            public ResultSetExtractor<String> single() {
                return resultSet -> resultSet.next() ? this.extractScalar(resultSet) : "";
            }
        };
    }

    public static <T extends Enum<T>> ScalarMapper<T> mapToEnum(
            int columnIndex,
            Class<T> clazz
    ) {
        rejectIfNull(clazz, "clazz");

        return new ScalarMapper<>(columnIndex) {
            @Override
            protected T extractScalar(
                    ResultSet resultSet
            ) {
                return JdbcMapper.getEnum(resultSet, clazz, getColumnIndex());
            }
        };
    }

    public static ScalarMapper<Object[]> mapToObjects() {
        return new ScalarMapper<>() {
            @Override
            protected Object[] extractScalar(
                    ResultSet resultSet
            ) {
                try {
                    final var size = resultSet.getMetaData().getColumnCount();
                    final var result = new Object[size];
                    for (var i = 0; i < size; ++i) {
                        result[i] = resultSet.getObject(i + 1);
                        if (resultSet.wasNull()) {
                            result[i] = null;
                        }
                    }
                    return result;
                } catch (SQLException ex) {
                    throw new DataRetrievalFailureException("Cannot extract row from result set", ex);
                }
            }
        };
    }
}
